import events from 'events';

// LinkParser
//
// https://github.com/ravisorg/LinkParser
//
// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and
// IPv6, unrecognised TLDs, and more.
//
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
// http://creativecommons.org/licenses/by-sa/4.0/
(function () {
    // Original URL regex from the Android android.text.util.Linkify function, found here:
    // http://stackoverflow.com/a/19696443
    //
    // However there were problems with it, most probably related to the fact it was
    // written in 2007, and it's been highly modified.
    //
    // 1) I didn't like the fact that it was tied to specific TLDs, since new ones
    // are being added all the time it wouldn't be reasonable to expect developer to
    // be continually updating their regular expressions.
    //
    // 2) It didn't allow unicode characters in the domains which are now allowed in
    // many languages, (including some IDN TLDs). Again these are constantly being
    // added to and it doesn't seem reasonable to hard-code them. Note this ended up
    // not being possible in standard JS due to the way it handles multibyte strings.
    // It is possible using XRegExp, however a big performance hit results. Disabled
    // for now.
    //
    // 3) It didn't allow for IPv6 hostnames
    // IPv6 regex from http://stackoverflow.com/a/17871737
    //
    // 4) It was very poorly commented
    //
    // 5) It wasn't as smart as it could have been about what should be part of a
    // URL and what should be part of human language.

    const protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)';
    const credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
        + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
        + '\\@)';

    // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
    // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
    // http://intermapper.com/
    const ipv6 = '('
        + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))'
        + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
        + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
        + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
        + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
        + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
        + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
        + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
        + ')(%.+)?';

    const ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.'
        + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
        + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
        + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])';

    // This would have been a lot cleaner if JS RegExp supported conditionals...
    const linkRegExpString =

        // begin match for protocol / username / password / host
        '(?:'

        // ============================
        // If we have a recognized protocol at the beginning of the URL, we're
        // more relaxed about what we accept, because we assume the user wants
        // this to be a URL, and we're not accidentally matching human language
        + protocols + '?'

        // optional username:password@
        + credentials + '?'

        // IP address (both v4 and v6)
        + '(?:'

        // IPv6
        + ipv6

        // IPv4
        + '|' + ipv4

        + ')'

        // end match for protocol / username / password / host
        + ')'

        // optional port number
        + '(?:\\:\\d{1,5})?'

        // plus optional path and query params (no unicode allowed here?)
        + '(?:'
        + '\\/(?:'
        // some characters we'll accept because it's unlikely human language
        // would use them after a URL unless they were part of the url
        + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])'
        + '|(?:\\%[a-f0-9]{2})'
        // some characters are much more likely to be used AFTER a url and
        // were not intended to be included in the url itself. Mostly end
        // of sentence type things. It's also likely that the URL would
        // still work if any of these characters were missing from the end
        // because we parsed it incorrectly. For these characters to be accepted
        // they must be followed by another character that we're reasonably
        // sure is part of the url
        + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
        + ')*'
        + '|\\b|\$'
        + ')';

    // regex = XRegExp(regex,'gi');
    const linkRegExp = RegExp(linkRegExpString, 'gi');

    const protocolRegExp = RegExp('^' + protocols, 'i');

    // if url doesn't begin with a known protocol, add http by default
    function ensureProtocol(url) {
        if (!url.match(protocolRegExp)) {
            url = 'http://' + url;
        }
        return url;
    }

    // look for links in the text
    const LinkParser = {
        parse: function (text) {
            const links = [];
            let match;

            // eslint-disable-next-line no-cond-assign
            while (match = linkRegExp.exec(text)) {
                console.debug(match);
                const txt = match[0];
                const pos = match.index;
                const len = txt.length;
                const url = ensureProtocol(text);
                links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url });
            }

            return links;
        }

    };

    window.LinkParser = LinkParser;
})();

let cache = {};

// TODO: Replace with isIP (https://www.npmjs.com/package/is-ip)
function isValidIpAddress(address) {
    const links = LinkParser.parse(address);

    return links.length == 1;
}

// TODO: Add IPv6 support. Potentially replace with isLocalhost (https://www.npmjs.com/package/is-localhost-ip)
function isLocalIpAddress(address) {
    address = address.toLowerCase();

    if (address.includes('127.0.0.1')) {
        return true;
    }
    if (address.includes('localhost')) {
        return true;
    }

    return false;
}

export function getServerAddress(apiClient) {
    const serverAddress = apiClient.serverAddress();

    if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) {
        return Promise.resolve(serverAddress);
    }

    const cachedValue = getCachedValue(serverAddress);
    if (cachedValue) {
        return Promise.resolve(cachedValue);
    }

    return apiClient.getEndpointInfo().then(function (endpoint) {
        if (endpoint.IsInNetwork) {
            return apiClient.getPublicSystemInfo().then(function (info) {
                let localAddress = info.LocalAddress;
                if (!localAddress) {
                    console.debug('No valid local address returned, defaulting to external one');
                    localAddress = serverAddress;
                }
                addToCache(serverAddress, localAddress);
                return localAddress;
            });
        } else {
            addToCache(serverAddress, serverAddress);
            return serverAddress;
        }
    });
}

function clearCache() {
    cache = {};
}

function addToCache(key, value) {
    cache[key] = {
        value: value,
        time: new Date().getTime()
    };
}

function getCachedValue(key) {
    const obj = cache[key];

    if (obj && (new Date().getTime() - obj.time) < 180000) {
        return obj.value;
    }

    return null;
}

events.on(window.connectionManager, 'localusersignedin', clearCache);
events.on(window.connectionManager, 'localusersignedout', clearCache);

export default {
    getServerAddress: getServerAddress
};
